/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.env.Environment;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * @author Spencer Gibb
 * <p>
 *     ShortcutConfigurable 是 Spring Cloud Gateway 中的一个核心接口，用于支持 快捷配置（Shortcut Configuration）。
 *     它允许开发者通过简化的方式定义路由规则和过滤器配置，而无需显式地指定所有参数。
 *
 *1. 核心功能
 * 		1.1 快捷配置
 * 			Spring Cloud Gateway 提供了两种配置方式：
 *			 完整配置：显式地指定所有参数。
 * 			 快捷配置：通过简化的语法快速定义路由或过滤器。
 * 				ShortcutConfigurable 的作用是标准化快捷配置的处理逻辑，确保不同组件可以以一致的方式解析简化配置。
 * 		1.2 动态解析
 * 			它支持动态解析参数值，例如通过 SpEL（Spring Expression Language）表达式计算参数的实际值。
 * 			这使得配置更加灵活，能够根据运行时环境动态调整。
 * 		1.3 参数映射
 *			 ShortcutConfigurable 提供了一种机制，将简化的键值对配置映射到目标对象的字段中。
 * 			例如，用户可以通过 key=value 的形式定义配置，而底层会自动将其转换为目标对象的属性。
 *
 *4. 使用场景
 * 4.1 路由谓词工厂（RoutePredicateFactory）
 * 路由谓词工厂通常实现了 ShortcutConfigurable 接口，以便支持快捷配置。
 * 示例：
 * spring:
 *   cloud:
 *     gateway:
 *       routes:
 *         - id: example_route
 *           uri: http://example.org
 *           predicates:
 *             - Path=/example/**
 *在这种情况下，Path 谓词会通过 ShortcutConfigurable 的逻辑解析参数。
 *
 * 4.2 网关过滤器工厂（GatewayFilterFactory）
 * 网关过滤器工厂也可以实现 ShortcutConfigurable 接口，以便支持快捷配置。
 * spring:
 *   cloud:
 *     gateway:
 *       routes:
 *         - id: example_route
 *           uri: http://example.org
 *           filters:
 *             - AddRequestHeader=X-Custom-Header, custom-value
 * 5. 关键点总结
 * 5.1 核心职责
 * 规范化键名和参数值。
 * 支持动态解析 SpEL 表达式。
 * 提供多种快捷类型的标准化处理逻辑。
 * 5.2 使用范围
 * 主要用于路由谓词工厂和网关过滤器工厂。
 * 适用于需要支持快捷配置的场景。
 * 5.3 扩展性
 * 可以通过实现 ShortcutConfigurable 接口或扩展 ShortcutType 枚举来支持更多类型的快捷配置。
 * </p>
 */
public interface ShortcutConfigurable {

	/**
	 *
	 功能：规范化键名。
	 场景：
	 	如果键名是自动生成的（如 generatedNamePrefix），则用 shortcutFieldOrder 中的字段名替换。
	 	确保键名与目标对象的字段名一致。
	 关键逻辑：
	 	检查键名是否以 GENERATED_NAME_PREFIX 开头。
	 	如果满足条件，则用 shortcutFieldOrder 中对应位置的字段名替换。

	 * @param key
	 * @param entryIdx
	 * @param argHints
	 * @param args
	 * @return
	 */
	static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints, Map<String, String> args) {
		// RoutePredicateFactory has name hints and this has a fake key name
		// replace with the matching key hint
		if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argHints.shortcutFieldOrder().isEmpty()
				&& entryIdx < args.size() && entryIdx < argHints.shortcutFieldOrder().size()) {
			key = argHints.shortcutFieldOrder().get(entryIdx);
		}
		return key;
	}

	/**
	 * 功能：解析参数值。
	 * 场景：
	 * 		支持普通字符串值。
	 * 		支持 SpEL 表达式解析值。
	 * 关键逻辑：
	 * 		如果值以 #{ 开头并以 } 结尾，则认为是 SpEL 表达式。
	 * 		使用 GatewayEvaluationContext 解析表达式并返回实际值。
	 * @param parser
	 * @param beanFactory
	 * @param entryValue
	 * @return
	 */
	static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) {
		Object value;
		String rawValue = entryValue;
		if (rawValue != null) {
			rawValue = rawValue.trim();
		}
		if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
			// assume it's spel
			GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory);
			Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
			value = expression.getValue(context);
		}
		else {
			value = entryValue;
		}
		return value;
	}

	/**
	 * 功能：定义快捷配置的类型。
	 * 场景：
	 * 		默认类型为 DEFAULT。
	 * 		可以扩展为其他类型（如 GATHER_LIST 或 GATHER_LIST_TAIL_FLAG）。
	 * @return
	 */
	default ShortcutType shortcutType() {
		return ShortcutType.DEFAULT;
	}

	/**
	 * Returns hints about the number of args and the order for shortcut parsing.
	 * 功能：定义快捷配置的字段顺序。
	 * 场景：
	 * 		用于映射简化配置中的参数到目标对象的字段。
	 * 		返回一个字段名列表，按顺序匹配参数。
	 * @return the list of hints
	 */
	default List<String> shortcutFieldOrder() {
		return Collections.emptyList();
	}

	/**
	 * 功能：定义字段前缀。
	 * 场景：
	 * 		如果字段名需要统一添加前缀，可以通过此方法实现。
	 * @return
	 */
	default String shortcutFieldPrefix() {
		return "";
	}

	enum ShortcutType {

		/**
		 * Default shortcut type.
		 * 功能：默认快捷类型。
		 * 场景：
		 * 		将键值对直接映射到目标对象的字段。
		 * 		支持动态解析 SpEL 表达式。
		 *
		 * 示例：
		 * spring:
		 *   cloud:
		 *     gateway:
		 *       routes:
		 *         - id: example_route
		 *           uri: http://example.org
		 *           predicates:
		 *             - Path=/example/**
		 */
		DEFAULT {
			@Override
			public Map<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf,
					SpelExpressionParser parser, BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				int entryIdx = 0;
				for (Map.Entry<String, String> entry : args.entrySet()) {
					String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args);
					Object value = getValue(parser, beanFactory, entry.getValue());

					map.put(key, value);
					entryIdx++;
				}
				return map;
			}
		},

		/**
		 * List shortcut type.
		 * 功能：将多个值收集为列表。
		 * 场景：
		 * 		如果某个字段需要接收多个值，则可以使用此类型。
		 * 示例：
		 * spring:
		 *   cloud:
		 *     gateway:
		 *       routes:
		 *         - id: example_route
		 *           uri: http://example.org
		 *           filters:
		 *             - AddRequestHeader=header1, value1, value2, value3
		 */
		GATHER_LIST {
			@Override
			public Map<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf,
					SpelExpressionParser parser, BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				// field order should be of size 1
				List<String> fieldOrder = shortcutConf.shortcutFieldOrder();
				Assert.isTrue(fieldOrder != null && fieldOrder.size() == 1,
						"Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1");
				String fieldName = fieldOrder.get(0);
				map.put(fieldName,
						args.values()
							.stream()
							.map(value -> getValue(parser, beanFactory, value))
							.collect(Collectors.toList()));
				return map;
			}
		},

		/**
		 * List is all elements except last which is a boolean flag.
		 * 功能：将多个值收集为列表，并用最后一个值作为布尔标志。
		 * 场景：
		 * 如果需要在列表末尾添加一个布尔标志（如启用/禁用某个功能），可以使用此类型。
		 * 示例：
		 * spring:
		 *   cloud:
		 *     gateway:
		 *       routes:
		 *         - id: example_route
		 *           uri: http://example.org
		 *           filters:
		 *             - Retry=5, true
		 */
		GATHER_LIST_TAIL_FLAG {
			@Override
			public Map<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf,
					SpelExpressionParser parser, BeanFactory beanFactory) {
				Map<String, Object> map = new HashMap<>();
				// field order should be of size 1
				List<String> fieldOrder = shortcutConf.shortcutFieldOrder();
				Assert.isTrue(fieldOrder != null && fieldOrder.size() == 2,
						"Shortcut Configuration Type GATHER_LIST_HEAD must have shortcutFieldOrder of size 2");
				List<String> values = new ArrayList<>(args.values());
				if (!values.isEmpty()) {
					// strip boolean flag if last entry is true or false
					int lastIdx = values.size() - 1;
					String lastValue = values.get(lastIdx);
					if ("true".equalsIgnoreCase(lastValue) || "false".equalsIgnoreCase(lastValue)
							|| lastValue == null) {
						values = values.subList(0, lastIdx);
						map.put(fieldOrder.get(1), getValue(parser, beanFactory, lastValue));
					}
				}
				String fieldName = fieldOrder.get(0);
				map.put(fieldName,
						values.stream()
							.map(value -> getValue(parser, beanFactory, value))
							.collect(Collectors.toList()));
				return map;
			}
		};

		public abstract Map<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf,
				SpelExpressionParser parser, BeanFactory beanFactory);

	}

	class GatewayEvaluationContext implements EvaluationContext {

		private final BeanFactoryResolver beanFactoryResolver;

		private final SimpleEvaluationContext delegate;

		public GatewayEvaluationContext(BeanFactory beanFactory) {
			this.beanFactoryResolver = new BeanFactoryResolver(beanFactory);
			Environment env = beanFactory.getBean(Environment.class);
			boolean restrictive = env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled",
					Boolean.class, true);
			if (restrictive) {
				delegate = SimpleEvaluationContext.forPropertyAccessors(new RestrictivePropertyAccessor())
					.withMethodResolvers((context, targetObject, name, argumentTypes) -> null)
					.build();
			}
			else {
				delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build();
			}
		}

		@Override
		public TypedValue getRootObject() {
			return delegate.getRootObject();
		}

		@Override
		public List<PropertyAccessor> getPropertyAccessors() {
			return delegate.getPropertyAccessors();
		}

		@Override
		public List<ConstructorResolver> getConstructorResolvers() {
			return delegate.getConstructorResolvers();
		}

		@Override
		public List<MethodResolver> getMethodResolvers() {
			return delegate.getMethodResolvers();
		}

		@Override
		@Nullable
		public BeanResolver getBeanResolver() {
			return this.beanFactoryResolver;
		}

		@Override
		public TypeLocator getTypeLocator() {
			return delegate.getTypeLocator();
		}

		@Override
		public TypeConverter getTypeConverter() {
			return delegate.getTypeConverter();
		}

		@Override
		public TypeComparator getTypeComparator() {
			return delegate.getTypeComparator();
		}

		@Override
		public OperatorOverloader getOperatorOverloader() {
			return delegate.getOperatorOverloader();
		}

		@Override
		public void setVariable(String name, Object value) {
			delegate.setVariable(name, value);
		}

		@Override
		@Nullable
		public Object lookupVariable(String name) {
			return delegate.lookupVariable(name);
		}

	}

	class RestrictivePropertyAccessor extends ReflectivePropertyAccessor {

		@Override
		public boolean canRead(EvaluationContext context, Object target, String name) {
			return false;
		}

	}

}
